#include "userprog/syscall.h"
#include <stdio.h>
#include <syscall-nr.h>
#include "threads/interrupt.h"
#include "threads/thread.h"
#include "lib/user/syscall.h"
#include "threads/vaddr.h"
#include "devices/shutdown.h"
#include "filesys/filesys.h"
#include "threads/synch.h"
#include "lib/kernel/list.h"
#include "filesys/file.h"
#include "devices/shutdown.h"
#include "threads/malloc.h"
#include "lib/string.h"
#include "process.h"
#include "vm/frame.h"
#include "vm/page.h"

#define MAX_WRITE_BUFFER 128
#define MAX_FILENAME_SIZE 14

static void syscall_handler (struct intr_frame *);
static void syscall_execute(struct intr_frame *f);
static int get_arg_integer(const unsigned int index, const void *stack_ptr);
static void* get_arg_pointer(const unsigned int index, const void *stack_ptr);
static struct file * retrieve_file(const int fd);

// a global variable used to represent the id map
static int mapid=1;


void
syscall_init (void) 
{
	intr_register_int (0x30, 3, INTR_ON, syscall_handler, "syscall");
}


static
bool is_pointer_valid(const void *pointer)
{
	if(pointer == NULL)
	{
		//printf("POINTER INVALID 1\n");
		return false;
	}

	if(! is_user_vaddr(pointer))
	{
		//printf("POINTER INVALID 2\n");
		return false;
	}

	//printf("test 1: %d\n", pointer <= 0x08084000-64*1024*1024);
	//printf("test 2: %d\n", pagedir_mapped(thread_current()->pagedir,pointer)==0);
	//printf("test 3: %d\n", pointer == NULL);
	//printf("test 4: %d\n", pointer >= PHYS_BASE);


	if(pointer <= 0x08084000-64*1024*1024 ||
			pagedir_mapped(thread_current()->pagedir,pointer) == 0 ||
			pointer == NULL || pointer >= PHYS_BASE) {
		//printf("POINTER INVALID 3\n");
		return false;
	}


	/*if(pagedir_get_page(thread_current()->pagedir, pointer) == NULL)
	{
		return false;
	}*/

	return true;
}


static
int get_arg_integer(const unsigned int index, const void *stack_ptr)
{
	if(!is_pointer_valid(stack_ptr+index*4)) {
		exit(-1);
	}
	return *((int *)(stack_ptr + (4 * index)));
}

/*
 * Gets the pointer argument from the stack,
 * need to also check if the pointer is a valid pointer
 */
static
void* get_arg_pointer(const unsigned int index, const void *stack_ptr)
{
	if(!is_pointer_valid(stack_ptr+index*4))
	{
		exit(-1);
	}

	const void *pointer = *((void **)(stack_ptr + index * 4));
	if (! is_pointer_valid(pointer))
	{
		exit(-1);
	}

	return pointer;
}

/*
 * Gets the pointer argument from the stack,
 * this is a special case for mmap
 */
static
void* get_arg_pointer_mmap(const unsigned int index, const void *stack_ptr)
{
	const void *pointer = *((void **)(stack_ptr + index * 4));

	// check if the pointer is valid (pointer + PGSIZE) > PHYS_BASE ||
//	if(pointer == 0 || (pg_round_down(pointer) != pointer) ||
//			 pointer < 0x08050000) {
//		return -1;
//	}

	return pointer;
}

/*
 * Execute syscall function
 */
static
void syscall_execute(struct intr_frame *f)
{
	if(! is_pointer_valid(f->esp))
	{
		exit(-1);
	}

	const int  sysno      = *((int *) f->esp);
	const void *stack_ptr = f->esp + 4;

	if(! is_pointer_valid(stack_ptr))
	{
		exit(-1);
	}

	switch(sysno)
	{
		case SYS_HALT: // 0
			halt();
			break;
		case SYS_EXIT: // 1
		{
			const int status = get_arg_integer(0, stack_ptr);
			exit(status);
			break;
		}
		case SYS_EXEC: // 2
		{
			const char *cmd = (char *) get_arg_pointer(0, stack_ptr);
			f->eax = exec(cmd);
			break;
		}
		case SYS_WAIT: // 3
		{
			const int pid = get_arg_integer(0, stack_ptr);
			f->eax = wait(pid);
			break;
		}
		case SYS_CREATE: // 4
		{
			const char                *file = (char *)get_arg_pointer(0, stack_ptr);
			const unsigned int initial_size = get_arg_integer(1, stack_ptr);
			f->eax = create(file, initial_size);
			break;

		}
		case SYS_REMOVE: // 5
		{
			const char *file = (char *)get_arg_pointer(0, stack_ptr);
			f->eax = remove(file);
			break;
		}
		case SYS_OPEN: // 6
		{
			const char *file = (char *)get_arg_pointer(0, stack_ptr);
			f->eax = open(file);
			break;
		}
		case SYS_FILESIZE: // 7
		{
			const int fd = get_arg_integer(0, stack_ptr);
			f->eax = filesize(fd);
			break;
		}
		case SYS_READ: // 8
		{
			const int              fd = get_arg_integer(0, stack_ptr);
			      void        *buffer = get_arg_pointer(1, stack_ptr);
			const unsigned int length = get_arg_integer(2, stack_ptr);
			f->eax = read(fd, buffer, length);
			break;
		}
		case SYS_WRITE: // 9
		{
			const int              fd = get_arg_integer(0, stack_ptr);
			const void        *buffer = get_arg_pointer(1, stack_ptr);
			const unsigned int length = get_arg_integer(2, stack_ptr);

			f->eax = write(fd, buffer, length);

			break;
		}
		case SYS_SEEK:
		{
			const int                fd = get_arg_integer(0, stack_ptr);
			const unsigned int position = get_arg_integer(1, stack_ptr);
			seek(fd, position);
			break;
		}
		case SYS_TELL:
		{
			const int fd = get_arg_integer(0, stack_ptr);
			f->eax = tell(fd);
			break;
		}
		case SYS_CLOSE:
		{
			const int fd = get_arg_integer(0, stack_ptr);
			close(fd);
			break;
		}
		case SYS_MMAP:
		{
			//printf("MMAP Called!\n");
			const int fd = get_arg_integer(0, stack_ptr);
			//printf("MMAP Called2!\n");
			const void *map = get_arg_pointer_mmap(1, stack_ptr);
			f->eax = mmap(fd, map);
			//printf("MMAP Called4!");
			break;
		}
		case SYS_MUNMAP:
		{
			//printf("MUNMAP Called!\n");
			// const void *map = get_arg_pointer(0, stack_ptr);
			// TODO: Rethink argument validation for MUNMAP
			const mapid_t map = *((mapid_t *)(stack_ptr));
			//printf("MUNMAP Called2!\n");
			munmap(map);
		    break;
		}
	}
}


static
void syscall_handler (struct intr_frame *f)
{
	syscall_execute(f);
}


static
struct file * retrieve_file(const int fd)
{
	struct list *fd_list = &thread_current()->fd_list;

	struct list_elem *elem;
	for(elem = list_begin(fd_list); elem != list_end(fd_list); elem = list_next(elem))
	{
        struct file_descriptor *fd_struct = list_entry (elem, struct file_descriptor, fd_elem);
        if(fd_struct->fd == fd)
        {
        	return fd_struct->file;
        }
	}

	return NULL;
}

void halt ()
{
	shutdown_power_off();
}

/*
 * Call for EXIT Syscall function
 */
void exit (const int status)
{
	printf("EXIT\n");
	thread_current()->exit_status = status;

	// Remove all file entries in the file descriptor
	struct thread *ct = thread_current();

    while (!list_empty (&ct->fd_list))
	{
	    struct list_elem        *e = list_pop_front (&ct->fd_list);
		struct file_descriptor *fd = list_entry(e, struct file_descriptor, fd_elem);
		close(fd->fd);
	}

    // deallocate all members of the sub thread list
    while (!list_empty (&ct->sub_threads))
	{
	    struct list_elem  *e  = list_pop_front (&ct->sub_threads);
	    struct sub_thread *st = list_entry(e, struct sub_thread, s_t_elem);

	    struct thread *child = get_thread_by_pid(st->pid);
	    if(child != NULL)
	    {
		    child->is_waited_by_parent = false;
		    child->pt = NULL;
	    }

	    free(st);
	}

	// notify parent thread the exit code
    if(ct->pt != NULL)
    {
    	struct thread    *pt = ct->pt;
    	struct list      *stl = &pt->sub_threads;
    	struct list_elem *stle;

    	for(stle = list_begin(stl); stle != list_end(stl); stle = list_next(stle))
    	{
    		struct sub_thread *st = list_entry(stle, struct sub_thread, s_t_elem);
    		if (st->pid == thread_current()->tid)
    		{
    			st->exit_code = status;
    			break;
    		}
    	}
    }


	// close executable
	sema_down(&fs_sema);
		file_close(thread_current()->exe);
	sema_up(&fs_sema);

	printf("END EXIT\n");

	// Print termination message
	printf ("%s: exit(%d)\n", ct->name, ct->exit_status);
	thread_exit();
}

/*
 * Call for EXEC Syscall function
 */
pid_t exec (const char *file)
{
	printf("EXEC\n");
	sema_down(&fs_sema);
	struct file *exec_file;

	char *t_file = strchr(file, (int)(' '));
	// Indicates that there is a trim on the file name
	if (t_file != NULL)
	{
		char file_name[t_file - file];
		strlcpy(file_name, file, (t_file - file + 1));
		exec_file = filesys_open(file_name);
	}
	// Filename is not trimmed
	else
	{
		exec_file = filesys_open(file);
	}

	if (exec_file == NULL)
	{
		if (t_file != NULL)
		{
			char file_name[t_file - file];
			strlcpy(file_name, file, (t_file - file + 1));
			printf("load: %s: open failed\n", file_name);
		}
		else
		{
			printf("load: %s: open failed\n", file);
		}
		printf("END EXEC\n");
		sema_up(&fs_sema);
		return -1;
	}
	else
	{
		int status = process_execute(file);
		file_close(exec_file);
		sema_up(&fs_sema);
		printf("END EXEC\n");
		return status;
	}
	sema_up(&fs_sema);

	printf("END EXEC\n");

	return -1;
}

/*
 * Call for WAIT Syscall
 */
int wait (const pid_t pidt)
{
	return process_wait(pidt);
}

bool create (const char *file, const unsigned initial_size)
{
	printf("cREATE\n");
	if(strlen(file) > 0)
	{
		if(strlen(file) <= MAX_FILENAME_SIZE)
		{
			sema_down(&fs_sema);
			bool success = filesys_create(file, initial_size);
			sema_up(&fs_sema);
			return success;
		}
	}
	printf("END CREATE\n");
	return false;
}

bool remove (const char *file)
{
	sema_down(&fs_sema);
    struct list_elem *se;
    struct spt_elem *spte;
    bool deny = false;
    for(se = list_begin(&thread_current()->spt); se != list_end(&thread_current()->spt); se = list_next(se)) {
		spte=(struct spt_elem *)list_entry (se, struct spt_elem, elem);
		if(spte->fileptr == file)
		{
				// Temporary close
				spte->needremove=true;
				deny=true;
				break;
		}
	}

    if(deny) {
    	sema_up(&fs_sema);
    	return false;
    }

	const bool result = filesys_remove(file);
	sema_up(&fs_sema);
	return result;
}

// file already checked in syscallhandler
int open (const char *file)
{
	printf("OPEN\n");
	struct thread *t = thread_current();

	t->fd_id_counter++;

	sema_down(&fs_sema);
	struct file* f_open = filesys_open(file);
	sema_up(&fs_sema);

	if(f_open == NULL)
	{
		return -1;
	}

	struct file_descriptor *fd = malloc(sizeof(struct file_descriptor));
	if(fd == NULL)
	{
		// not enough memory for allocation
		sema_down(&fs_sema);
		file_close(f_open);
		sema_up(&fs_sema);
		return -1;
	}

	//printf("CHECKPOINT 3!\n");
	fd->fd   = t->fd_id_counter;

	fd->file = f_open;
	fd->file_name = malloc((strlen(file) + 1) * sizeof(char));
	if(fd->file_name != NULL)
	{
		strlcpy(fd->file_name, file, strlen(file) + 1);
		list_push_front(&t->fd_list, &fd->fd_elem);
	}

	//printf("CHECKPOINT 4!\n");
	//printf("File Descriptor: %d\n",fd->fd);


	printf("END OPEN\n");

	return fd->fd ;
}

/*
 * Call for FILESIZE Syscall function
 */
int filesize (const int fd)
{
	struct file *file = retrieve_file(fd);
	if (file != NULL)
	{
		sema_down(&fs_sema);
		int length = file_length(file);
		sema_up(&fs_sema);
		return length;
	}

	return -1;
}

/*
 * Call for READ Syscall function
 */

int read (const int fd, void *buffer, const unsigned size)
{
	if (buffer == NULL)
	{
		return -1;
	}

	if(sizeof(buffer) == 0)
	{
		return -1;
	}

	// indicating input read from keyboard
	if (fd == STDIN_FILENO)
	{
		int no_bytes;
		for(no_bytes = 0; no_bytes < size; no_bytes++)
		{
			uint8_t input = input_getc();
			char *tmp_buffer = (char *)buffer;
			tmp_buffer [no_bytes] = (char)input;
		}
		return no_bytes;
	}
	// indicating input read from file
	else
	{
		struct file * file = retrieve_file(fd);
		if(file != NULL)
		{
			sema_down(&fs_sema);
			off_t no_bytes = file_read(file, buffer, size);
			sema_up(&fs_sema);
			return no_bytes;
		}
	}
	return -1;
}

/*
 * Call for WRITE Syscall function
 */
int write (const int fd, const void *buffer, unsigned size)
{
	printf("WRITE\n");
	if (buffer != NULL)
	{
		if(sizeof(buffer) > 0)
		{
			// Indicating we want to write to the console
			if(fd == 1)
			{
				// Write only to a predesignated MAX_WRITE_BUFFER
				// This is to avoid message interleaving
				int no_bytes = 0;
				while (size > MAX_WRITE_BUFFER)
				{
					char *tmp_buffer = (char *)buffer;
					putbuf(tmp_buffer, MAX_WRITE_BUFFER);
					size -= MAX_WRITE_BUFFER;
					tmp_buffer += MAX_WRITE_BUFFER;
					no_bytes += MAX_WRITE_BUFFER;
				}
				putbuf(buffer, size);
				no_bytes += size;
				printf("END WRITE 3\n");
				return no_bytes;
			}
			// Indicating we want to write to a file
			else
			{
				struct file *file = retrieve_file(fd);
				if (file != NULL)
				{
					sema_down(&fs_sema);
					off_t no_bytes = file_write(file, buffer, size);
					sema_up(&fs_sema);
					printf("END WRITE 2\n");
					return no_bytes;
				}
			}
		}
	}

	printf("END WRITE 1\n");
	return -1;
}


void seek (const int fd, const unsigned position)
{
	const struct file *file = retrieve_file(fd);
	if(file == NULL)
	{
		return;
	}

	sema_down(&fs_sema);
	file_seek(file, position);
	sema_up(&fs_sema);
}

/*
 * Call for TELL Syscall function
 */
unsigned tell (const int fd)
{
	struct file *file = retrieve_file(fd);
	off_t       fp    = -1;

	if(file != NULL)
	{
		sema_down(&fs_sema);
		fp = file_tell(file);
		sema_up(&fs_sema);
	}

	return fp;
}


void close (const int fd)
{
	printf("CLOSE\n");
	struct list_elem *se;
	struct spt_elem *spte;
	bool deny;

	if(fd == STDIN_FILENO || fd == STDOUT_FILENO)
	{
		return;
	}

	struct file *f = retrieve_file(fd);
	if(f == NULL)
	{
		return;
	}

//	sema_down(&fs_sema);
//	file_close(f);
//	sema_up(&fs_sema);

	struct list *fd_list = &thread_current()->fd_list;

	struct list_elem *elem;
	for(elem = list_begin(fd_list); elem != list_end(fd_list); elem = list_next(elem))
	{
		struct file_descriptor *tmp_fd = list_entry (elem, struct file_descriptor, fd_elem);

        if(tmp_fd->fd == fd)
        {
        	list_remove(elem);

        	deny = false;
			for(se = list_begin(&thread_current()->spt); se != list_end(&thread_current()->spt); se = list_next(se))
			{
				spte = (struct spt_elem *)list_entry (se, struct spt_elem, elem);
				if(spte->fileptr == tmp_fd->file)
				{
					spte->needclose = true;
					deny = true;
					//break;
				}
			}

        	tmp_fd->fd_elem.next =  NULL;
        	tmp_fd->fd_elem.prev = NULL;
        	tmp_fd->file         = NULL;

       	    free(tmp_fd->file_name);
        	free(tmp_fd);

        	break;
        }
	}

	if(! deny)
	{
		sema_down(&fs_sema);
		file_close(f);
		sema_up(&fs_sema);
	}

	printf("END CLOSE\n");
}

mapid_t mmap (int fd, void* data)
{
	printf("MMAP\n");
	if(data == NULL)
	{
		// Invalid address
		return MAP_FAILED;
	}

	if(pg_round_down(data) != data)
	{
		// pointer must be page aligned
		return MAP_FAILED;
	}

	if(data < 0x08050000)
	{
		return MAP_FAILED;
	}

	if((data + PGSIZE) >= PHYS_BASE)
	{
		return MAP_FAILED;
	}

//	// check if the pointer is valid (pointer + PGSIZE) > PHYS_BASE ||
//	if(pointer == 0 || (pg_round_down(pointer) != pointer) ||
//			 pointer < 0x08050000) {


	if(fd == 0 || fd == 1)
	{
		// STDIN and STDOUT are not mappable
		return MAP_FAILED;
	}

	printf("MMAP 2\n");

	struct list_elem *e;
	struct list_elem *se;
	struct list_elem *te;
	struct file_descriptor *file_d;
	struct list* fd_list = &thread_current()->fd_list;
	bool findornot = false;
	struct spt_elem *spte;
	struct spt_elem *spte2;
	off_t filesize;

	for (e = list_begin (fd_list); e != list_end (fd_list); e = list_next (e))
	{
		 file_d = list_entry (e, struct file_descriptor, fd_elem);
		 if (file_d->fd == fd)
		 {
			 findornot = true;
			 filesize = file_length (file_d->file);

			 if(filesize == 0)
			 {
				 printf("RETURN MMAP\n");

				 // file to be mapped must be greater than 0 bytes
				 return MAP_FAILED;
			 }

			 int mapped_page = 0;
			 off_t fileoff = 0;
			 uint32_t upage= data;
			 lock_acquire(&thread_current()->spt_list_lock);
			 while(filesize>0)
			 {
				 spte = (struct spt_elem *)malloc(sizeof(struct spt_elem));
				 spte->upage = upage;
				 for (se = list_begin (&thread_current()->spt); se != list_end (&thread_current()->spt);
					  se = list_next (se))
				 {
					 spte2=(struct spt_elem *)list_entry (se, struct spt_elem, elem);
					 if(spte2->upage == spte->upage)
					 {
						 free(spte); // TODO: Test by benni
						 lock_release(&thread_current()->spt_list_lock); // TODO: Test by benni
						 return -1;
					 }
				 }
				 upage = upage + (uint32_t)PGSIZE;
				 spte->fileptr = file_d->file;
				 spte->ofs=mapped_page * (uint32_t)PGSIZE;
				 mapped_page++;
				 spte->mapid = mapid;


				 if(filesize>=PGSIZE)
				 {
					 spte->read_bytes = PGSIZE;
					 spte->zero_bytes = 0;
				 }
				 else
				 {
					 spte->read_bytes = filesize;
					 spte->zero_bytes = PGSIZE-filesize;
				 }

				 spte->writable=true;
				 list_push_back (&thread_current()->spt, &spte->elem);
				 filesize=filesize-PGSIZE;
			 }
			 lock_release(&thread_current()->spt_list_lock);
			 break;
		 }
	}

	 if(findornot)
	 {
		 printf("RETURN MMAP 2\n");
		 return mapid;
	 }
	 else
	 {
		 printf("RETURN MMAP 3\n");
		 return -1;
	 }
}

void munmap (mapid_t map) {
	int mip = map;
	struct thread* t = thread_current();
	struct spt_elem *spte;
	struct list_elem *e;
	struct list_elem *e2;
	lock_acquire(&thread_current()->spt_list_lock);
	e = list_begin(&t->spt);

	 while(e != list_end(&t->spt))
	 {
		 spte = (struct spt_elem *)list_entry (e, struct spt_elem, elem);
		 //printf("CHECK MapID:%d == %d?\n",mip,spte->mapid);
		 if(spte->mapid == mip)
		 {
			 //printf("CHECK2 MApID:%d!\n",spte->mapid);
			 if(pagedir_is_dirty(t->pagedir,spte->upage))
			 {
				 file_write_at(spte->fileptr, spte->upage, PGSIZE, spte->ofs);
			 }

			 if(spte->needclose)
			 {
				 file_close(spte->fileptr);
			 }

			 e2 = e;
			 e = list_next(e);
			 list_remove(e2);
			 free(spte); // TODO: Test by Benni
		 }
		 else
		 {
			 e = list_next(e);
		 }
	 }

	 lock_release(&thread_current()->spt_list_lock);
}
